package com.alibaba.otter.manager.web.utils;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public enum ExpressEvaluators {

    EVALUATOR;

    private final ExpressionParser parser = new SpelExpressionParser();
    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>(64);


    public <T> T execute(Method method,Object target,Object[] args,String expression,Class<T> resultType){
        Class<?> targetClass = getTargetClass(target);
        EvaluationContext evaluationContext = createEvaluationContext(method, args, target, targetClass);
        return getExpression(expression).getValue(evaluationContext,resultType);
    }

    public <T> T execute(String expression,Class<T> resultType){
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        return getExpression(expression).getValue(evaluationContext,resultType);
    }

    public <T> T execute(String expression,Object rootObject,Map<String,Object> vars,Class<T> resultType){
        EvaluationContext evaluationContext = new StandardEvaluationContext(rootObject);
        vars.forEach(evaluationContext::setVariable);
        return getExpression(expression).getValue(evaluationContext,resultType);
    }

    public <T> T execute(String expression,Map<String,Object> vars,Class<T> resultType){
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        vars.forEach(evaluationContext::setVariable);
        return getExpression(expression).getValue(evaluationContext,resultType);
    }

    private EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Class<?> targetClass){

        Method targetMethod =  (!Proxy.isProxyClass(targetClass) ?
                AopUtils.getMostSpecificMethod(method, targetClass) : method);
        CommonMethodRootObject rootObject = new CommonMethodRootObject(targetMethod, args, target, targetClass);
        return new MethodBasedEvaluationContext(rootObject,targetMethod, args,parameterNameDiscoverer);
    }


    private Class<?> getTargetClass(Object target) {
        return AopProxyUtils.ultimateTargetClass(target);
    }

    /**
     * Return the {@link Expression} for the specified SpEL value
     * <p>Parse the expression if it hasn't been already.
     * @param expression the expression to parse
     */
    private Expression getExpression(String expression) {

        Expression expr = expressionCache.get(expression);
        if (expr == null) {
            expr = parser.parseExpression(expression);
            expressionCache.put(expression, expr);
        }
        return expr;
    }


    public record CommonMethodRootObject(Method method, Object[] args, Object target, Class<?> targetClass) { }
}
